package com.orion.config.security;

import com.orion.service.impl.AuthReactiveUserDetailService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;

import javax.annotation.Resource;

/**
 * GATEWAY采用的是webflux，不用用常规的那套springsecurity方法，详看WebFluxSecurityConfiguration-springSecurityFilterChain
 *
 * @author Administrator
 * @date 2021/11/5
 */
@EnableWebFluxSecurity
public class SecurityConfig {

    @Resource(name = "authReactiveUserDetailService")
    private AuthReactiveUserDetailService authReactiveUserDetailService;

    @Resource(name = "customServerAuthenticationEntryPoint")
    private ServerAuthenticationEntryPoint customServerAuthenticationEntryPoint;

    @Resource(name = "customServerAccessDeniedHandler")
    private ServerAccessDeniedHandler customServerAccessDeniedHandler;

    @Resource(name = "refreshTokenWebFilter")
    private RefreshTokenWebFilter refreshTokenWebFilter;

    @Resource(name = "accessTokenWebFilter")
    private AccessTokenWebFilter accessTokenWebFilter;

    @Resource(name = "customServerSuccessHandler")
    private CustomServerSuccessHandler customServerSuccessHandler;

    @Resource(name = "customServerFailureHandler")
    private CustomServerFailureHandler customServerFailureHandler;

    @Value("${security.white-paths}")
    private String[] whitePaths;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 如果没有，系统会自动创建一个
     */
    @Bean
    public ReactiveAuthenticationManager customAuthenticationManager() {
        UserDetailsRepositoryReactiveAuthenticationManager manager
                = new UserDetailsRepositoryReactiveAuthenticationManager(authReactiveUserDetailService);
        manager.setPasswordEncoder(passwordEncoder());
        return manager;
    }

    @Bean
    ServerSecurityContextRepository customServerSecurityContextRepository(){
        return NoOpServerSecurityContextRepository.getInstance();
    }


    @Bean
    public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {
        http
                .authorizeExchange()
                //白名单不拦截
                .pathMatchers(whitePaths).permitAll()
                //AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全
                .pathMatchers(HttpMethod.OPTIONS).permitAll().and()
                .authorizeExchange()
                .pathMatchers("/cs/apollo/**").hasAuthority("lll")
                .anyExchange().authenticated()
                .and()
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .authenticationManager(customAuthenticationManager())
                .securityContextRepository(customServerSecurityContextRepository())
                .cors()
                .and()
                .formLogin().loginPage("/orionLogin")
                .authenticationSuccessHandler(customServerSuccessHandler)
                .authenticationFailureHandler(customServerFailureHandler)
                .and()
                .logout().logoutUrl("/orionLogout").logoutSuccessHandler(new CustomServerLogoutHandler())
                .and()
                .exceptionHandling()
                //hasAuthority没权限也不走这个，只走entryPoint，因为他最后抛AuthenticationCredentialsNotFoundException异常
                .accessDeniedHandler(customServerAccessDeniedHandler)
                .authenticationEntryPoint(customServerAuthenticationEntryPoint);

        http.addFilterAt(refreshTokenWebFilter, SecurityWebFiltersOrder.HTTP_BASIC);
        http.addFilterAt(accessTokenWebFilter, SecurityWebFiltersOrder.AUTHORIZATION);

        return http.build();
    }
}
